/*****************************************************************************/
/*                                                                           */
/*                                 litpool.c                                 */
/*                                                                           */
/*              Literal string handling for the cc65 C compiler              */
/*                                                                           */
/*                                                                           */
/*                                                                           */
/* (C) 1998-2013, Ullrich von Bassewitz                                      */
/*                Roemerstrasse 52                                           */
/*                D-70794 Filderstadt                                        */
/* EMail:         uz@cc65.org                                                */
/*                                                                           */
/*                                                                           */
/* This software is provided 'as-is', without any expressed or implied       */
/* warranty.  In no event will the authors be held liable for any damages    */
/* arising from the use of this software.                                    */
/*                                                                           */
/* Permission is granted to anyone to use this software for any purpose,     */
/* including commercial applications, and to alter it and redistribute it    */
/* freely, subject to the following restrictions:                            */
/*                                                                           */
/* 1. The origin of this software must not be misrepresented; you must not   */
/*    claim that you wrote the original software. If you use this software   */
/*    in a product, an acknowledgment in the product documentation would be  */
/*    appreciated but is not required.                                       */
/* 2. Altered source versions must be plainly marked as such, and must not   */
/*    be misrepresented as being the original software.                      */
/* 3. This notice may not be removed or altered from any source              */
/*    distribution.                                                          */
/*                                                                           */
/*****************************************************************************/

#include <stdio.h>
#include <string.h>

/* common */
#include "attrib.h"
#include "check.h"
#include "coll.h"
#include "tgttrans.h"
#include "xmalloc.h"

/* cc65 */
#include "asmlabel.h"
#include "codegen.h"
#include "error.h"
#include "global.h"
#include "litpool.h"

/*****************************************************************************/
/*                                   Data                                    */
/*****************************************************************************/

/* Definition of a literal */
struct Literal {
  unsigned Label; /* Asm label for this literal */
  int RefCount;   /* Reference count */
  int Output;     /* True if output has been generated */
  StrBuf Data;    /* Literal data */
};

/* Definition of the literal pool */
struct LiteralPool {
  struct SymEntry *Func;       /* Function that owns the pool */
  Collection WritableLiterals; /* Writable literals in the pool */
  Collection ReadOnlyLiterals; /* Readonly literals in the pool */
};

/* The global and current literal pool */
static LiteralPool *GlobalPool = 0;
static LiteralPool *LP = 0;

/* Stack that contains the nested literal pools. Since TOS is in LiteralPool
** and functions aren't nested in C, the maximum depth is 1. I'm using a
** collection anyway, so the code is prepared for nested functions or
** whatever.
*/
static Collection LPStack = STATIC_COLLECTION_INITIALIZER;

/*****************************************************************************/
/*                              struct Literal                               */
/*****************************************************************************/

static Literal *NewLiteral(const void *Buf, unsigned Len)
/* Create a new literal and return it */
{
  /* Allocate memory */
  Literal *L = xmalloc(sizeof(*L));

  /* Initialize the fields */
  L->Label = GetLocalLabel();
  L->RefCount = 0;
  L->Output = 0;
  SB_Init(&L->Data);
  SB_AppendBuf(&L->Data, Buf, Len);

  /* Return the new literal */
  return L;
}

static void FreeLiteral(Literal *L)
/* Free a literal */
{
  /* Free the literal data */
  SB_Done(&L->Data);

  /* Free the structure itself */
  xfree(L);
}

static void OutputLiteral(Literal *L)
/* Output one literal to the currently active data segment */
{
  /* Translate the literal into the target charset */
  TranslateLiteral(L);

  /* Define the label for the literal */
  g_defdatalabel(L->Label);

  /* Output the literal data */
  g_defbytes(SB_GetConstBuf(&L->Data), SB_GetLen(&L->Data));

  /* Mark the literal as output */
  L->Output = 1;
}

Literal *UseLiteral(Literal *L)
/* Increase the reference counter for the literal and return it */
{
  /* Increase the reference count */
  ++L->RefCount;

  /* If --local-strings was given, immediately output the literal */
  if (IS_Get(&LocalStrings)) {
    /* Switch to the proper data segment */
    if (IS_Get(&WritableStrings)) {
      g_usedata();
    } else {
      g_userodata();
    }
    /* Output the literal */
    OutputLiteral(L);
  }

  /* Return the literal */
  return L;
}

void ReleaseLiteral(Literal *L)
/* Decrement the reference counter for the literal */
{
  --L->RefCount;
  CHECK(L->RefCount >= 0);
}

void TranslateLiteral(Literal *L)
/* Translate a literal into the target charset. */
{
  TgtTranslateBuf(SB_GetBuf(&L->Data), SB_GetLen(&L->Data));
}

unsigned GetLiteralLabel(const Literal *L)
/* Return the asm label for a literal */
{
  return L->Label;
}

const char *GetLiteralStr(const Literal *L)
/* Return the data for a literal as pointer to char */
{
  return SB_GetConstBuf(&L->Data);
}

const StrBuf *GetLiteralStrBuf(const Literal *L)
/* Return the data for a literal as pointer to the string buffer */
{
  return &L->Data;
}

unsigned GetLiteralSize(const Literal *L)
/* Get the size of a literal string */
{
  return SB_GetLen(&L->Data);
}

/*****************************************************************************/
/*                                   Code                                    */
/*****************************************************************************/

static LiteralPool *NewLiteralPool(struct SymEntry *Func)
/* Create a new literal pool and return it */
{
  /* Allocate memory */
  LiteralPool *LP = xmalloc(sizeof(*LP));

  /* Initialize the fields */
  LP->Func = Func;
  InitCollection(&LP->WritableLiterals);
  InitCollection(&LP->ReadOnlyLiterals);

  /* Return the new pool */
  return LP;
}

static void FreeLiteralPool(LiteralPool *LP)
/* Free a LiteralPool structure */
{
  /* Free the collections contained within the struct */
  DoneCollection(&LP->WritableLiterals);
  DoneCollection(&LP->ReadOnlyLiterals);

  /* Free the struct itself */
  xfree(LP);
}

static int Compare(void *Data attribute((unused)), const void *Left, const void *Right)
/* Compare function used when sorting the literal pool */
{
  /* Larger strings are considered "smaller" */
  return (int)GetLiteralSize(Right) - (int)GetLiteralSize(Left);
}

void InitLiteralPool(void)
/* Initialize the literal pool */
{
  /* Create the global literal pool */
  GlobalPool = LP = NewLiteralPool(0);
}

void PushLiteralPool(struct SymEntry *Func)
/* Push the current literal pool onto the stack and create a new one */
{
  /* We must have a literal pool to push! */
  PRECONDITION(LP != 0);

  /* Push the old pool */
  CollAppend(&LPStack, LP);

  /* Create a new one */
  LP = NewLiteralPool(Func);
}

LiteralPool *PopLiteralPool(void)
/* Pop the last literal pool from TOS and activate it. Return the old
** literal pool.
*/
{
  /* Remember the current literal pool */
  LiteralPool *Old = LP;

  /* Pop one from stack */
  LP = CollPop(&LPStack);

  /* Return the old one */
  return Old;
}

static void MoveLiterals(Collection *Source, Collection *Target)
/* Move referenced literals from Source to Target, delete unreferenced ones */
{
  unsigned I;

  /* Move referenced literals, remove unreferenced ones */
  for (I = 0; I < CollCount(Source); ++I) {

    /* Get the literal */
    Literal *L = CollAt(Source, I);

    /* If it is referenced and not output, add it to the Target pool,
    ** otherwise free it
    */
    if (L->RefCount && !L->Output) {
      CollAppend(Target, L);
    } else {
      FreeLiteral(L);
    }
  }
}

void MoveLiteralPool(LiteralPool *LocalPool)
/* Move all referenced literals in LocalPool to the global literal pool. This
** function will free LocalPool after moving the used string literals.
*/
{
  /* Move the literals */
  MoveLiterals(&LocalPool->WritableLiterals, &GlobalPool->WritableLiterals);
  MoveLiterals(&LocalPool->ReadOnlyLiterals, &GlobalPool->ReadOnlyLiterals);

  /* Free the local literal pool */
  FreeLiteralPool(LocalPool);
}

static void OutputWritableLiterals(Collection *Literals)
/* Output the given writable literals */
{
  unsigned I;

  /* If nothing there, exit... */
  if (CollCount(Literals) == 0) {
    return;
  }

  /* Switch to the correct segment */
  g_usedata();

  /* Emit all literals that have a reference */
  for (I = 0; I < CollCount(Literals); ++I) {

    /* Get a pointer to the literal */
    Literal *L = CollAtUnchecked(Literals, I);

    /* Output this one, if it has references and wasn't already output */
    if (L->RefCount > 0 && !L->Output) {
      OutputLiteral(L);
    }
  }
}

static void OutputReadOnlyLiterals(Collection *Literals)
/* Output the given readonly literals merging (even partial) duplicates */
{
  unsigned I;

  /* If nothing there, exit... */
  if (CollCount(Literals) == 0) {
    return;
  }

  /* Switch to the correct segment */
  g_userodata();

  /* Sort the literal pool by literal size. Larger strings go first */
  CollSort(Literals, Compare, 0);

  /* Emit all literals that have a reference */
  for (I = 0; I < CollCount(Literals); ++I) {

    unsigned J;
    Literal *C;

    /* Get the next literal */
    Literal *L = CollAt(Literals, I);

    /* Ignore it, if it doesn't have references or was already output */
    if (L->RefCount == 0 || L->Output) {
      continue;
    }

    /* Translate the literal into the target charset */
    TranslateLiteral(L);

    /* Check if this literal is part of another one. Since the literals
    ** are sorted by size (larger ones first), it can only be part of a
    ** literal with a smaller index.
    ** Beware: Only check literals that have actually been referenced.
    */
    C = 0;
    for (J = 0; J < I; ++J) {

      const void *D;

      /* Get a pointer to the compare literal */
      Literal *L2 = CollAt(Literals, J);

      /* Ignore literals that have no reference */
      if (L2->RefCount == 0) {
        continue;
      }

      /* Get a pointer to the data */
      D = SB_GetConstBuf(&L2->Data) + SB_GetLen(&L2->Data) - SB_GetLen(&L->Data);

      /* Compare the data */
      if (memcmp(D, SB_GetConstBuf(&L->Data), SB_GetLen(&L->Data)) == 0) {
        /* Remember the literal and terminate the loop */
        C = L2;
        break;
      }
    }

    /* Check if we found a match */
    if (C != 0) {

      /* This literal is part of a longer literal, merge them */
      g_aliasdatalabel(L->Label, C->Label, GetLiteralSize(C) - GetLiteralSize(L));

    } else {

      /* Define the label for the literal */
      g_defdatalabel(L->Label);

      /* Output the literal data */
      g_defbytes(SB_GetConstBuf(&L->Data), SB_GetLen(&L->Data));
    }

    /* Mark the literal */
    L->Output = 1;
  }
}

void OutputLiteralPool(void)
/* Output the global literal pool */
{
  /* Output both sorts of literals */
  OutputWritableLiterals(&GlobalPool->WritableLiterals);
  OutputReadOnlyLiterals(&GlobalPool->ReadOnlyLiterals);
}

Literal *AddLiteral(const char *S)
/* Add a literal string to the literal pool. Return the literal. */
{
  return AddLiteralBuf(S, strlen(S) + 1);
}

Literal *AddLiteralBuf(const void *Buf, unsigned Len)
/* Add a buffer containing a literal string to the literal pool. Return the
** literal.
*/
{
  /* Create a new literal */
  Literal *L = NewLiteral(Buf, Len);

  /* Add the literal to the correct pool */
  if (IS_Get(&WritableStrings)) {
    CollAppend(&LP->WritableLiterals, L);
  } else {
    CollAppend(&LP->ReadOnlyLiterals, L);
  }

  /* Return the new literal */
  return L;
}

Literal *AddLiteralStr(const StrBuf *S)
/* Add a literal string to the literal pool. Return the literal. */
{
  return AddLiteralBuf(SB_GetConstBuf(S), SB_GetLen(S));
}
